// --- Copyright (c) 2006-2008 Stefan Kyntchev ---
// This software is written and copyrighted by Stefan Kyntchev 
// and BeyondPod Team members. All rights are reserved.
// Author contact: support@beyondpod.mobi
// ------------------------------------------------------------------------
// This file is part of BeyondPod RSS Feed Reader and Podcast manager
// (www.codeplex.com/BeyondPod) 
// BeyondPod is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
//  
// BeyondPod is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more details. 
//  
// You should have received a copy of the GNU General Public License
// along with BeyondPod. If not, see <http://www.gnu.org/licenses/>
// ------------------------------------------------------------------------
// COPYRIGHT NOTICE: This copyright notice may NOT be removed, obscured or modified 
// without written consent from the author.
// --- End copyright notice --- 


using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Xml;
using svs.Mobile.BeyondMedia.FeedCore.Properties;

namespace svs.Mobile.BeyondMedia.FeedCore.RSS.Parsers
{

	public class AttributeCollection : Dictionary<string, string>
	{
	}

	public delegate void ElementHandler(XmlReader aReader);

	public class ParserBase
	{
		static Dictionary<string, int> _timeZones = null;
		protected Dictionary<string, ElementHandler> m_handlers = new Dictionary<string, ElementHandler>();

		public void Handle(XmlReader aReader)
		{
			int depth = aReader.Depth;

			do
			{
				if (aReader.NodeType == XmlNodeType.EndElement && depth == aReader.Depth)
					return;

				if (aReader.NodeType == XmlNodeType.Element)
				{
					if (m_handlers.ContainsKey(aReader.LocalName))
					{
						ElementHandler h = m_handlers[aReader.LocalName];
						if (h != null)
						{
							h(aReader);
						}
					}

					if (!ContinueParsing())
					{
						aReader.Close();
						return;
					}
				}
			} while (aReader.Read());
		}

		protected virtual bool ContinueParsing()
		{
			return true;
		}

		protected static AttributeCollection ReadAttributes(XmlReader aReader)
		{
			AttributeCollection retVal = new AttributeCollection();

			while (aReader.MoveToNextAttribute())
			{
				retVal[aReader.LocalName] = aReader.Value;
			}

			return retVal;
		}

		protected static string ReadElementString(XmlReader reader)
		{
			if (reader.IsEmptyElement)
			{
				return string.Empty;
			}

			string result = reader.ReadString();

			while (reader.NodeType != XmlNodeType.EndElement)
			{
				reader.Skip();
				result += reader.ReadString();
			}

			return result;
		}

		protected static int? ReadInt(XmlReader reader)
		{
			string num = ReadElementString(reader);

			return ReadInt(num);
		}


		protected static int? ReadInt(string aNum)
		{
			if (string.IsNullOrEmpty(aNum))
				return null;

			try
			{
				int? retVal = int.Parse(aNum);
				return retVal;
			}
			catch
			{
				return null;
			}
		}

		/// <summary>
		/// Parses the DateTime. The returned value is in UTC time
		/// </summary>
		/// <param name="reader"></param>
		/// <returns></returns>
		protected static Guid ReadGuid(XmlReader reader)
		{
			string guid = ReadElementString(reader);
			try
			{
				return new Guid(guid);
			}
			catch (Exception)
			{
				return Guid.Empty;
			}
		}

		/// <summary>
		/// Parses the DateTime. The returned value is in UTC time
		/// </summary>
		protected static DateTime? ReadDateTime(XmlReader reader, out string originalDate)
		{
			originalDate = ReadElementString(reader);
			if (!string.IsNullOrEmpty(originalDate))
			{
				DateTime? retVal = Parse(originalDate);
				if (retVal.HasValue)
				{
					return retVal.Value;
				}
				else
					return null;
			}
			else
				return null;
		}

		static readonly Regex m_rfc2822 = new Regex(@"\s*(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*,\s*)?(\d{1,2})\s+(Jan|January|Feb|February|Mar|March|Apr|April|May|Jun|Jul|July|Aug|August|Sep|September|Oct|October|Nov|November|Dec|December)\s+(\d{2,})\s+(\d{1,2})\s*:\s*(\d{1,2})\s*(?::\s*(\d{1,2}))?\s+([+\-]\d{4}|UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Z])", RegexOptions.Compiled);
		static readonly ArrayList m_MonthAbbrArray = new ArrayList(new string[] { "ZeroIndex", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" });
		static readonly ArrayList m_MonthsArray = new ArrayList(new string[] { "ZeroIndex", "January", "February", "March", "April", "May", "Jun", "July", "August", "September", "October", "November", "December" });

		/// <summary>
		/// Parse is able to parse RFC2822/RFC822 formatted dates.
		/// It has a fallback mechanism: if the string does not match,
		/// the normal DateTime.Parse() function is called.
		/// 
		/// Copyright of RssBandit.org
		/// Author - t_rendelmann
		/// </summary>
		/// <param name="dateTime">Date Time to parse</param>
		/// <returns>DateTime instance</returns>
		/// <exception cref="FormatException">On format errors parsing the DateTime</exception>
		private static DateTime? Parse(string dateTime)
		{
			if (dateTime == null)
			{
				return DateTime.Now.ToUniversalTime();
			}

			if (dateTime.Trim().Length == 0)
			{
				return DateTime.Now.ToUniversalTime();
			}

			try
			{
				Match m = m_rfc2822.Match(dateTime);
				if (m.Success)
				{
					try
					{
						int dd = Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);

						int mth = m_MonthAbbrArray.IndexOf(m.Groups[2].Value);
						if(mth < 0)
							mth = m_MonthsArray.IndexOf(m.Groups[2].Value);

						int yy = Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture);
						//// following year completion is compliant with RFC 2822.
						yy = (yy < 50 ? 2000 + yy : (yy < 1000 ? 1900 + yy : yy));
						int hh = Int32.Parse(m.Groups[4].Value, CultureInfo.InvariantCulture);
						int mm = Int32.Parse(m.Groups[5].Value, CultureInfo.InvariantCulture);
						int ss = Int32.Parse(m.Groups[6].Value, CultureInfo.InvariantCulture);
						string zone = m.Groups[7].Value;

						DateTime xd = new DateTime(yy, mth, dd, hh, mm, ss);
						return xd.AddHours(RFCTimeZoneToGMTBias(zone)*-1);
					}
					catch (FormatException)
					{
						return null;
					}
				}

				// fall-back, if regex does not match:
				return DateTime.Parse(dateTime, CultureInfo.InvariantCulture).ToUniversalTime();
			}
			catch
			{
				return DateTime.Now.ToUniversalTime();
			}
		}

		/// <summary>
		/// Changes Time zone based on GMT
		/// 
		/// Copyright of RssBandit.org
		/// Author - t_rendelmann
		/// </summary>
		/// <param name="zone">The zone.</param>
		/// <returns>RFCTimeZoneToGMTBias</returns>
		private static double RFCTimeZoneToGMTBias(string zone)
		{
			if (_timeZones == null)
			{
				_timeZones = new Dictionary<string, int>();
				_timeZones.Add("GMT", 0);
				_timeZones.Add("UT", 0);
				_timeZones.Add("EST", -5 * 60);
				_timeZones.Add("EDT", -4 * 60);
				_timeZones.Add("CST", -6 * 60);
				_timeZones.Add("CDT", -5 * 60);
				_timeZones.Add("MST", -7 * 60);
				_timeZones.Add("MDT", -6 * 60);
				_timeZones.Add("PST", -8 * 60);
				_timeZones.Add("PDT", -7 * 60);
				_timeZones.Add("Z", 0);
				_timeZones.Add("A", -1 * 60);
				_timeZones.Add("B", -2 * 60);
				_timeZones.Add("C", -3 * 60);
				_timeZones.Add("D", -4 * 60);
				_timeZones.Add("E", -5 * 60);
				_timeZones.Add("F", -6 * 60);
				_timeZones.Add("G", -7 * 60);
				_timeZones.Add("H", -8 * 60);
				_timeZones.Add("I", -9 * 60);
				_timeZones.Add("K", -10 * 60);
				_timeZones.Add("L", -11 * 60);
				_timeZones.Add("M", -12 * 60);
				_timeZones.Add("N", 1 * 60);
				_timeZones.Add("O", 2 * 60);
				_timeZones.Add("P", 3 * 60);
				_timeZones.Add("Q", 4 * 60);
				_timeZones.Add("R", 3 * 60);
				_timeZones.Add("S", 6 * 60);
				_timeZones.Add("T", 3 * 60);
				_timeZones.Add("U", 8 * 60);
				_timeZones.Add("V", 3 * 60);
				_timeZones.Add("W", 10 * 60);
				_timeZones.Add("X", 3 * 60);
				_timeZones.Add("Y", 12 * 60);
			}

			if (zone.IndexOfAny(new char[] { '+', '-' }) == 0)  // +hhmm format
			{
				int sign = (zone[0] == '-' ? -1 : 1);
				string s = zone.Substring(1).TrimEnd();
				int hh = Math.Min(23, Int32.Parse(s.Substring(0, 2), CultureInfo.InvariantCulture));
				int mm = Math.Min(59, Int32.Parse(s.Substring(2, 2), CultureInfo.InvariantCulture));
				return sign * (hh + (mm / 60.0));
			}
			else
			{ // named format
				string s = zone.ToUpper(CultureInfo.InvariantCulture).Trim();
				foreach (string key in _timeZones.Keys)
				{
					if (key.Equals(s))
					{
						return _timeZones[key] / 60.0;
					}
				}
			}

			return 0.0;
		}



	}
}